Pour ce projet, je suis data scientist pour une entreprise qui propose des crédits. L'entreprise souhaite développer un modèle de scoring de la probabilité de défaut du client et l'associer à un tableau de bord interactif afin que les responsables de la relation client puissent expliquer avec la plus grande transparence les décisions d'octroi ou non d'un crédit.
Dans ce notebook, j'effectue le prétraitement des données et une exploration de celles-ci.
# General
# File system management
import os
import glob
# Visualisation
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
import pandas as pd
import seaborn as sns
import missingno as msno
import math
import scipy
import scipy.stats as stats
from scipy.stats import variation
import collections
from collections import Counter
from termcolor import colored
from sklearn.impute import KNNImputer
from sklearn.preprocessing import LabelEncoder
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
%matplotlib inline
# Format & option
sns.set(rc={"figure.figsize": (16, 9)})
pd.options.display.max_columns = 150
# Style use
sns.set_style("darkgrid")
plt.style.use("ggplot")
sns.set_context("notebook", font_scale=1.5, rc={"lines.linewidth": 2.5})
%matplotlib inline
# Suppress warnings
import warnings
warnings.filterwarnings("ignore")
Les données sont fournies par Home Credit, un service dédié à la fourniture de lignes de crédit (prêts) à la population non bancarisée. Prédire si un client remboursera ou non un prêt ou s'il aura des difficultés est un besoin commercial essentiel.
"""from google.colab import drive
drive.mount('/content/drive')
%cd /content/drive/My\Drive/Data_projet_OC
print(
os.listdir(
r"/content/drive/MyDrive/Data_projet_OC/"
)
)"""
#List of files.
print(
os.listdir(
r"/Users/amandinelecerfdefer/Desktop/Formation_Data_Scientist_OC/WORK-projet7/Data"
)
)
Il y a un total de 9 fichiers : 1 fichier principal pour l'entraînement (avec la cible) 1 fichier principal pour le test (sans la cible), 1 fichier de description, et 6 autres fichiers contenant des informations supplémentaires sur chaque prêt.
application_{train|test}.csv les principales données de formation et de test contenant des informations sur chaque demande de prêt chez Home Credit. Chaque prêt a sa propre ligne et est identifié par la caractéristique SK_ID_CURR. Les données de demande d'entraînement sont accompagnées de la caractéristique TARGET indiquant 0 : le prêt a été remboursé ou 1 : le prêt n'a pas été remboursé.
bureau.csv données concernant les crédits antérieurs du client auprès d'autres institutions financières. Chaque crédit précédent a sa propre ligne dans bureau, mais un prêt dans les données de la demande peut avoir plusieurs crédits précédents.
bureau_balance.csv données mensuelles concernant les crédits précédents dans le bureau. Chaque ligne correspond à un mois de crédit antérieur, et un crédit antérieur unique peut avoir plusieurs lignes, une pour chaque mois de la durée du crédit.
POS_CASH_balance.csv données mensuelles sur les prêts au point de vente ou au comptant que les clients ont eu avec Home Credit. Chaque ligne correspond à un mois d'un prêt au point de vente ou d'un prêt en espèces précédent, et un seul prêt précédent peut avoir plusieurs lignes.
credit_card_balance.csv données mensuelles sur les cartes de crédit que les clients ont eu avec Home Credit. Chaque ligne correspond à un mois de solde de carte de crédit, et une seule carte de crédit peut avoir plusieurs lignes.
previous_application.csv demandes précédentes de prêts au Home Credit des clients qui ont des prêts dans les données de demande. Chaque prêt actuel dans les données de la demande peut avoir plusieurs prêts précédents. Chaque demande précédente a une ligne et est identifiée par la caractéristique SK_ID_PREV.
installments_payments.csv historique des paiements pour les prêts précédents chez Home Credit. Il y a une ligne pour chaque paiement effectué et une ligne pour chaque paiement manqué.
HomeCredit_columns_description.csv Ce fichier contient les descriptions des colonnes des différents fichiers de données.
Existing relationships between the different files
Train Test
default_dir = (
"/Users/amandinelecerfdefer/Desktop/Formation_Data_Scientist_OC/WORK-projet7/Data"
)
"""default_dir = (
"/content/drive/MyDrive/Data_projet_OC/")"""
app_train = pd.read_csv(os.path.join(default_dir, "application_train.csv"))
app_test = pd.read_csv(os.path.join(default_dir, "application_test.csv"))
print(f"Training Data Shape: {app_train.shape}")
print(f"Testing Data Shape: {app_test.shape}")
app_train.head()
Le CSV train contient la cible, contrairement au CSV test.
def get_balance_data():
pos_dtype = {
"SK_ID_PREV": np.uint64, "SK_ID_CURR": np.uint64, "MONTHS_BALANCE": np.int64, "SK_DPD": np.int64,
"SK_DPD_DEF": np.int64, "CNT_INSTALMENT": np.float64, "CNT_INSTALMENT_FUTURE": np.float64,
}
install_dtype = {
"SK_ID_PREV": np.uint64, "SK_ID_CURR": np.uint64, "NUM_INSTALMENT_NUMBER": np.int64, "NUM_INSTALMENT_VERSION": np.float64,
"DAYS_INSTALMENT": np.float64, "DAYS_ENTRY_PAYMENT": np.float64, "AMT_INSTALMENT": np.float64, "AMT_PAYMENT": np.float64
}
card_dtype = {
"SK_ID_PREV": np.uint64, "SK_ID_CURR": np.uint64, "MONTHS_BALANCE": np.int64, "AMT_CREDIT_LIMIT_ACTUAL": np.int64,
"CNT_DRAWINGS_CURRENT": np.int64, "SK_DPD": np.int64, "SK_DPD_DEF": np.int64, "AMT_BALANCE": np.float64,
"AMT_DRAWINGS_ATM_CURRENT": np.float64, "AMT_DRAWINGS_CURRENT": np.float64, "AMT_DRAWINGS_OTHER_CURRENT": np.float64,
"AMT_DRAWINGS_POS_CURRENT": np.float64, "AMT_INST_MIN_REGULARITY": np.float64, "AMT_PAYMENT_CURRENT": np.float64,
"AMT_PAYMENT_TOTAL_CURRENT": np.float64, "AMT_RECEIVABLE_PRINCIPAL": np.float64, "AMT_RECIVABLE": np.float64,
"AMT_TOTAL_RECEIVABLE": np.float64, "CNT_DRAWINGS_ATM_CURRENT": np.float64, "CNT_DRAWINGS_OTHER_CURRENT": np.float64,
"CNT_DRAWINGS_POS_CURRENT": np.float64, "CNT_INSTALMENT_MATURE_CUM": np.float64
}
bureau_dtype = {
"SK_ID_BUREAU": np.uint64, "SK_ID_CURR": np.uint64, "DAYS_CREDIT": np.int64, "CREDIT_DAY_OVERDUE": np.int64,
"DAYS_CREDIT_ENDDATE": np.float64, "DAYS_ENDDATE_FACT": np.float64, "AMT_CREDIT_MAX_OVERDUE": np.float64,
"CNT_CREDIT_PROLONG": np.int64, "AMT_CREDIT_SUM": np.float64, "AMT_CREDIT_SUM_DEBT": np.float64,
"AMT_CREDIT_SUM_LIMIT": np.float64, "AMT_CREDIT_SUM_OVERDUE": np.float64, "DAYS_CREDIT_UPDATE": np.int64,
"AMT_ANNUITY": np.float64
}
previous_application_dtype = {
"SK_ID_PREV": np.uint64, "SK_ID_CURR": np.uint64, "AMT_ANNUITY": np.float64, "AMT_APPLICATION": np.float64,
"AMT_CREDIT": np.float64, "AMT_DOWN_PAYMENT": np.float64, "AMT_GOODS_PRICE": np.float64, "HOUR_APPR_PROCESS_START": np.float64,
"NFLAG_LAST_APPL_IN_DAY": np.float64, "RATE_DOWN_PAYMENT": np.float64, "RATE_INTEREST_PRIMARY": np.float64,
"RATE_INTEREST_PRIVILEGED": np.float64, "DAYS_DECISION": np.int64, "SELLERPLACE_AREA": np.int64,
"CNT_PAYMENT": np.float64, "DAYS_FIRST_DRAWING": np.float64, "DAYS_FIRST_DUE": np.float64, "DAYS_LAST_DUE_1ST_VERSION": np.float64,
"DAYS_LAST_DUE": np.float64, "DAYS_TERMINATION": np.float64, "NFLAG_INSURED_ON_APPROVAL": np.float64
}
bureau_balance_dtype = {
"SK_ID_BUREAU": np.uint64, "MONTHS_BALANCE": np.int64
}
POS_CASH_balance = pd.read_csv(os.path.join(default_dir, "POS_CASH_balance.csv"), dtype=pos_dtype)
installments_payments = pd.read_csv(os.path.join(default_dir, "installments_payments.csv"), dtype=install_dtype)
credit_card_balance = pd.read_csv(os.path.join(default_dir, "credit_card_balance.csv"), dtype=card_dtype)
bureau = pd.read_csv(os.path.join(default_dir, "bureau.csv"), dtype=bureau_dtype)
previous_application = pd.read_csv(os.path.join(default_dir, "previous_application.csv"), dtype=previous_application_dtype)
bureau_balance = pd.read_csv(os.path.join(default_dir, "bureau_balance.csv"), dtype=bureau_balance_dtype)
return POS_CASH_balance, installments_payments, credit_card_balance, bureau, previous_application, bureau_balance
POS_CASH_balance, installments_payments, credit_card_balance, bureau, previous_application, bureau_balance = get_balance_data()
sample_submission = pd.read_csv(os.path.join(default_dir, "sample_submission.csv"))
pd.set_option("max_colwidth", 400)
HomeCredit_columns_description = pd.read_csv(os.path.join(default_dir, "HomeCredit_columns_description.csv"), encoding='mac_roman')
HomeCredit_columns_description
def data_describe(folder):
'''Check the number of rows, columns, missing values and duplicates.
Count type of columns.
Memory indication'''
data_dict = {}
for file in folder:
data = pd.read_csv(file, encoding='mac_roman')
data_dict[file] = [data.shape[0],
data.shape[1],
round(data.isna().sum().sum()/data.size*100, 2),
round(data.duplicated().sum().sum()/data.size*100, 2),
data.select_dtypes(include=['object']).shape[1],
data.select_dtypes(include=['float']).shape[1],
data.select_dtypes(include=['int']).shape[1],
data.select_dtypes(include=['bool']).shape[1],
round(data.memory_usage().sum()/1024**2, 3)]
comparative_table = pd.DataFrame.from_dict(data = data_dict,
columns = ['Rows', 'Columns', '%NaN', '%Duplicate',
'object_dtype','float_dtype', 'int_dtype',
'bool_dtype', 'MB_Memory'],
orient='index')
print("SUMMARY FILES…")
return(comparative_table)
"""#Data description
data_describe(glob.glob('/content/drive/MyDrive/Data_projet_OC/*.csv'))
#glob permet la recherche de tous les CSV par *.csv"""
#Data description
data_describe(glob.glob('/Users/amandinelecerfdefer/Desktop/Formation_Data_Scientist_OC/WORK-projet7/Data/*.csv'))
#glob permet la recherche de tous les CSV par *.csv
def features(folder):
'''Comparative data with missing values,
and many descriptive statistics.'''
data_object = {}
data_numeric = {}
for file in folder:
data = pd.read_csv(file, encoding='mac_roman')
data_object[file] = [(x, data[x].dtype,
data[x].isna().sum().sum(),
int(data[x].count())) for x in data.select_dtypes(exclude=['int', 'float'])]
data_numeric[file] = [(x, data[x].dtype,
int(data[x].isna().sum().sum()),
int(data[x].count()),
int(data[x].mean()),
round(data[x].std(),1),
round(data[x].min(),1),
round(data[x].max(),1)) for x in data.select_dtypes(exclude='object')]
comparative_object = pd.DataFrame.from_dict(data = data_object, orient='index')
dict_of_object = {name: pd.DataFrame(file) for name,file in data_object.items()}
df1 = pd.concat(dict_of_object, axis=0)
df1.columns=['features','dtype','nan','count']
comparative_numeric = pd.DataFrame.from_dict(data = data_numeric, orient='index')
dict_of_numeric = {name: pd.DataFrame(file) for name,file in data_numeric.items()}
df2 = pd.concat(dict_of_numeric, axis=0)
df2.columns=['features','dtype','nan','count', 'mean', 'std', 'min','max']
return df1, df2
"""#Data description
features(glob.glob('/content/drive/MyDrive/Data_projet_OC/*.csv'))[0]
#glob permet la recherche de tous les CSV par *.csv"""
#Data description
features(glob.glob('/Users/amandinelecerfdefer/Desktop/Formation_Data_Scientist_OC/WORK-projet7/Data/*.csv'))[0]
"""#Data description
features(glob.glob('/content/drive/MyDrive/Data_projet_OC/*.csv'))[1]
#glob permet la recherche de tous les CSV par *.csv"""
#Data description
features(folder=glob.glob('/Users/amandinelecerfdefer/Desktop/Formation_Data_Scientist_OC/WORK-projet7/Data/*.csv'))[1]
Il s'agit du tableau principal, divisé en deux fichiers pour Train (avec TARGET) et Test (sans TARGET). Données statistiques pour toutes les applications. Une ligne représente un prêt dans notre échantillon de données.
def informations(dataframe):
"""This function gives the general information of a dataset.
It returns the number of rows and columns of the dataset.
dataframe : dataset"""
print(colored("\n Overview of the dataset : \n", 'red'))
lines = dataframe.shape[0]
columns = dataframe.shape[1]
print(colored("The dataset has {} rows and {} "
"columns. \n \n".format(lines, columns), 'blue'))
print(colored("Column's name : \n", 'green'))
print(list(dataframe.columns))
print("\n")
print(colored("Column's Type : \n", 'green'))
print(list(dataframe.dtypes))
print("\n")
informations(app_train)
L'ensemble d'apprentissage comporte 307 511 observations (prêts) et 122 colonnes.
#Check if 'TARGET' is the only difference
print("Check theses which columns are differents in the two files.")
display(app_train.columns.difference(app_test.columns))
La TARGET est ce que l'on nous demande de prédire : soit un 0 pour le prêt a été remboursé à temps, soit un 1 indiquant que le client a eu des difficultés de paiement. Nous pouvons d'abord examiner le nombre de prêts entrant dans chaque catégorie.
app_train.groupby(['TARGET'])['SK_ID_CURR'].count()
def graphe_col_category(dataframe, col, size, name):
"""This function represents the categorical variables as a pie plot.
dataframe : dataset
size : size of the figure (X,X)"""
values = dataframe[col].value_counts()
labels = dataframe[col].value_counts().index
plt.figure(figsize=size)
#bar plot
plt.subplot(2, 2, 1)
sns.barplot(x=labels,
y=values,
palette='pink')
# Pie Plot
plt.subplot(2, 2, 2)
plt.title("Representation of the variable {}" .format(
col), fontsize=20)
plt.pie(values, labels=name,
autopct='%.1f%%', shadow=True, textprops={'fontsize': 20})
plt.axis('equal')
plt.tight_layout()
plt.legend()
plt.show()
graphe_col_category(app_train, 'TARGET', (20,20), ['No failure', 'failure'])
On remarque que les classes sont déséquilibrées, il y a beaucoup plus d'individus dans la classe 0 que de dans la classe 1. https://ichi.pro/fr/apprentissage-desequilibre-gerer-un-probleme-de-classe-desequilibre-71099907199579
ax, fig = plt.subplots(figsize=(20,8))
ax = sns.countplot(y='TARGET', data=app_train)
ax.set_title("TARGET distribution")
for p in ax.patches:
percentage = '{:.1f}%'.format(100 * p.get_width()/len(app_train.TARGET))
x = p.get_x() + p.get_width()
y = p.get_y() + p.get_height()/2
ax.annotate(percentage, (x, y), fontsize=20, fontweight='bold')
plt.show()
plt.figure(figsize=(20, 20))
plt.subplot(2, 2, 1)
sns.barplot(x=app_train['CODE_GENDER'].value_counts().index,
y=app_train['CODE_GENDER'].value_counts(), palette='pink').set_title("Gender distribution of individuals in the training set")
plt.subplot(2, 2, 2)
sns.barplot(x=app_test['CODE_GENDER'].value_counts().index,
y=app_test['CODE_GENDER'].value_counts(), palette='Blues').set_title("Gender distribution of individuals in the testing set")
On peut voir qu'il y a une troisième modalité pour le sexe (également présente pour d'autres colonnes) qui semble correspondre à des données manquantes (différentes notations entre les personnes).
app_train = app_train.replace('XNA', np.nan)
app_test = app_test.replace('XNA', np.nan)
plt.figure(figsize=(20, 20))
plt.subplot(2, 2, 1)
sns.barplot(x=app_train['CODE_GENDER'].value_counts().index,
y=app_train['CODE_GENDER'].value_counts(), palette='pink').set_title("Gender distribution of individuals in the training set")
plt.subplot(2, 2, 2)
sns.barplot(x=app_test['CODE_GENDER'].value_counts().index,
y=app_test['CODE_GENDER'].value_counts(), palette='Blues').set_title("Gender distribution of individuals in the testing set")
app_train['CODE_GENDER'].describe()
# number of columns of each type.
app_train.dtypes.value_counts()
Nombre unique d'entrées pour chaque colonne de chaque type de données
app_train.select_dtypes('object').apply(pd.Series.nunique)
La plupart des variables catégorielles ont un nombre relativement faible d'entrées uniques.
app_train.select_dtypes('int').apply(pd.Series.nunique)
app_train_int = list(app_train.select_dtypes('int'))
app_test_int = list(app_test.select_dtypes('int'))
app_train.select_dtypes('float').apply(pd.Series.nunique)
def pie_NaN(dataframe, size):
"""This function allows to make a pie plot showing the
proportion of missing data on the whole dataset.
dataframe : dataset
size : size of the figure (X,X)"""
lines = dataframe.shape[0]
columns = dataframe.shape[1]
# NAN data
nb_data = dataframe.count().sum()
# Total data = (colonnes*lignes)
nb_totale = (columns*lines)
# Filling rate
rate_dataOK = (nb_data/nb_totale)
print("The data set is filled in at {:.2%}".format(rate_dataOK))
print("and it has {:.2%} of missing data".format(1-rate_dataOK))
print("\n \n ")
# Pie Plot
rates = [rate_dataOK, 1 - rate_dataOK]
labels = ["Données", "NAN"]
explode = (0, 0.1)
colors = ['gold', 'pink']
# Plot
plt.figure(figsize=size)
plt.pie(rates, explode=explode, labels=labels, colors=colors,
autopct='%.2f%%', shadow=True, textprops={'fontsize': 26})
ttl = plt.title("Fill rate of the dataset", fontsize=32)
ttl.set_position([0.5, 0.85])
plt.axis('equal')
# ax.legend(labels, loc = "upper right", fontsize = 18)
plt.tight_layout()
plt.show()
pie_NaN(app_train, (10,10))
Dans le jeu de données de formation, il y a plus de 24% de données manquantes, voyons dans quelles colonnes précisément.
msno.matrix(app_train)
#Global view of the missing values (black)
plt.figure(figsize=(20,10))
sns.heatmap(app_train.notna(), cbar=False)
plt.show()
Les données manquantes sont plus fortement présentent sur les caractéristiques des habitats (et non sur les crédits).
# Function to calculate the % of missing data on the entered dataset.
def missing_values_table(df):
# Total missing values
mis_val = df.isnull().sum()
# Percentage of missing values
mis_val_percent = 100 * df.isnull().sum() / len(df)
# Make a table with the results
mis_val_table = pd.concat([mis_val, mis_val_percent], axis=1)
# Rename the columns
mis_val_table_ren_columns = mis_val_table.rename(
columns = {0 : 'Missing Values', 1 : '% of Total Values'})
# Sort the table by percentage of missing descending
mis_val_table_ren_columns = mis_val_table_ren_columns[
mis_val_table_ren_columns.iloc[:,1] != 0].sort_values(
'% of Total Values', ascending=False).round(1)
# Print some summary information
print ("Your selected dataframe has " + str(df.shape[1]) + " columns.\n"
"There are " + str(mis_val_table_ren_columns.shape[0]) +
" columns that have missing values.")
# Return the dataframe with missing information
return mis_val_table_ren_columns
missing_values = missing_values_table(app_train)
missing_values.head(20)
# NaN on categorical variables.
app_train.select_dtypes('object').isna().sum(axis=0)
# NaN on ints.
app_train.select_dtypes('int').isna().sum()
Il n'y a pas de données manquantes pour ce type de données.
# NaN on the floats.
app_train.select_dtypes('float').isna().sum()
list_names = ['app_train', 'app_test']
datasets = [app_train, app_test]
for name in list_names:
pos = list_names.index(name)
dataset = datasets[pos]
print("Duplicate of the dataset {}." .format(name))
print(dataset.duplicated('SK_ID_CURR').sum())
print("\n")
Il n'y a pas de doublon dans les jeux de données app_train et app_test.
Ces erreurs peuvent être dues à des chiffres mal saisis, à des erreurs dans l'équipement de mesure ou à des mesures valides mais extrêmes.
DAYS_BIRTH
Les chiffres de la colonne DAYS_BIRTH sont négatifs car ils sont enregistrés par rapport à la demande de prêt en cours. Pour voir ces statistiques en années, nous pouvons les multiplier par -1 et les diviser par le nombre de jours dans une année
app_train['DAYS_BIRTH'].describe()
Les valeurs sont négatives car enregistrées par rapport à la demande du prêt en cours. Il faut modifier ces dates pour plus de compréhension.
(app_train['DAYS_BIRTH'] / -365).describe()
En moyenne, les clients ont 43 ans, le plus jeune a 20 ans et le plus âgé 69 ans. 50% des clients ont moins de 43 ans. Nous pouvons donc dire que l'étude est principalement axée sur les personnes d'une quarantaine d'années.
app_train['AGE'] = round(app_train['DAYS_BIRTH'] / -365).astype('int')
app_test['AGE'] = round(app_test['DAYS_BIRTH'] / -365).astype('int')
app_train
# Distribution des âges. (en année)
plt.figure(figsize=(15,10))
sns.histplot(app_train['DAYS_BIRTH'] / -365, stat='count', color='red', kde="True")
plt.title('Age of Client')
plt.xlabel('Age (years)')
plt.ylabel('Count')
Il n'y a pas de valeurs aberrantes puisque tous les âges sont raisonnables
DAYS_EMPLOYED How many days before the application the person started current employment,time only relative to the application
app_train['DAYS_EMPLOYED'].describe()
app_test['DAYS_EMPLOYED'].describe()
plt.figure(figsize = (25, 15))
plt.subplot(2, 2, 1)
plt.title('Number of working day-train', weight='bold', size=18)
sns.distplot(app_train['DAYS_EMPLOYED'], kde=False, bins=30)
plt.subplot(2, 2, 2)
plt.title('Number of working day-test', weight='bold', size=18)
sns.distplot(app_test['DAYS_EMPLOYED'], kde=False, bins=30)
Ici avec cette analyse, on peut voir qu'il y a des données anormales car le maximun représente environ 100 ans de travail (ce qui est impossible).
anom = app_train[app_train['DAYS_EMPLOYED'] >= 350000]
non_anom = app_train[app_train['DAYS_EMPLOYED'] < 350000]
print('The non-anomalies default on %0.2f%% of loans' % (100 * non_anom['TARGET'].mean()))
print('The anomalies default on %0.2f%% of loans' % (100 * anom['TARGET'].mean()))
print('There are %d anomalous days of employment' % len(anom))
When there are no anomalies, there is an average of 8,66% default. The anomalies have 5,40% of default and therefore a lower rate. We will fill in the anomalous values with not a number (np.nan) and change the number of days to the average number of days worked.
anom['DAYS_EMPLOYED'].unique()
# Create an anomalous flag column
app_train['DAYS_EMPLOYED_ANOM'] = app_train["DAYS_EMPLOYED"] == 365243
app_train['DAYS_EMPLOYED_ANOM'] = app_train['DAYS_EMPLOYED_ANOM'].astype('object')
app_test['DAYS_EMPLOYED_ANOM'] = app_test["DAYS_EMPLOYED"] == 365243
app_test['DAYS_EMPLOYED_ANOM'] = app_test['DAYS_EMPLOYED_ANOM'].astype('object')
app_train.dtypes
# Replace the anomalous values with nan
app_train['DAYS_EMPLOYED'].replace({365243: np.nan}, inplace = True)
app_test["DAYS_EMPLOYED"].replace({365243: np.nan}, inplace = True)
plt.figure(figsize=(15,10))
sns.distplot(app_train['DAYS_EMPLOYED'], hist=True, rug=True, bins=25)
sns.distplot(app_test['DAYS_EMPLOYED'], hist=True, rug=True, bins=25)
plt.title('Histogram of DAYS_EMPLOYED after replacing anomalie with nan for the train and test set',
weight='bold', size=18)
plt.xlabel('Days Employment', weight="bold")
labels= ["Train", "Test"]
plt.legend(labels)
plt.show()
print('There are %d anomalies in the test data out of %d entries in the train set\n' % (app_train["DAYS_EMPLOYED"].isna().sum(), len(app_train)))
print('There are %d anomalies in the test data out of %d entries in the test set\n' % (app_test["DAYS_EMPLOYED"].isna().sum(), len(app_test)))
def test_train_col_category(dataframe_train, dataframe_test, col, size):
"""This function represents the categorical variables as a pie plot.
dataframe : dataset
size : size of the figure (X,X)"""
values_train = dataframe_train[col].value_counts()
labels_train = dataframe_train[col].value_counts().index
values_test = dataframe_test[col].value_counts()
labels_test = dataframe_test[col].value_counts().index
plt.figure(figsize=size)
#pie plot
plt.subplot(2, 2, 1)
plt.title("Representation of the variable {} for training set" .format(
col), fontsize=20)
plt.pie(values_train, labels=labels_train,
autopct='%.1f%%', shadow=True, textprops={'fontsize': 20})
# Pie Plot
plt.subplot(2, 2, 2)
plt.title("Representation of the variable {} for testing set" .format(
col), fontsize=20)
plt.pie(values_test, labels=labels_test,
autopct='%.1f%%', shadow=True, textprops={'fontsize': 20})
plt.axis('equal')
plt.tight_layout()
plt.legend()
plt.show()
def plot_stat(data, feature, title, size) :
ax, fig = plt.subplots(figsize=size)
ax = sns.countplot(y=feature, data=data, order=data[feature].value_counts(ascending=False).index)
ax.set_title(title)
for p in ax.patches:
percentage = '{:.1f}%'.format(100 * p.get_width()/len(data[feature]))
x = p.get_x() + p.get_width()
y = p.get_y() + p.get_height()/2
ax.annotate(percentage, (x, y), fontsize=20, fontweight='bold')
plt.show()
def plot_percent_target1(data, feature, title, size) :
cat_perc = data[[feature, 'TARGET']].groupby([feature],as_index=False).mean()
cat_perc.sort_values(by='TARGET', ascending=False, inplace=True)
ax, fig = plt.subplots(figsize=size)
ax = sns.barplot(y=feature, x='TARGET', data=cat_perc)
ax.set_title(title)
ax.set_xlabel("")
ax.set_ylabel("Percent of target with value 1")
for p in ax.patches:
percentage = '{:.1f}%'.format(100 * p.get_width())
x = p.get_x() + p.get_width()
y = p.get_y() + p.get_height()/2
ax.annotate(percentage, (x, y), fontsize=20, fontweight='bold')
plt.show()
Loan types - Distribution du type de prêts contractés
test_train_col_category(app_train, app_test, 'NAME_CONTRACT_TYPE', (20,20))
Les prêts renouvelables ne représentent que 10% du nombre total de prêts
plot_percent_target1(app_train, 'NAME_CONTRACT_TYPE',"Type of contract depend on Target1", (15,10))
La majorité des crédits non remboursés sont non renouvelables. (étude par rapport à leur fréquence d'apparition).
Client gender - Distribution H/F clients selon le remboursement du prêt
Précédement, nous avons pu voir que les clients du genre féminin sont deux fois plus présents que les clients masculins dans le jeu de données.
plot_percent_target1(app_train, 'CODE_GENDER',"Gender distribution depend on Target1", (15,10))
Les hommes ont tendance à moins rembourser leur crédits.
Flag own car - Distribution de la possession d'une voiture
test_train_col_category(app_train, app_test, 'FLAG_OWN_CAR', (20,20))
plot_percent_target1(app_train, 'FLAG_OWN_CAR',"Car owner depend on Target1", (15,10))
Le taux de non remboursement est de 8% que le client ait ou non une voiture.
Cnt Children - Distribution du nombre d'enfants
test_train_col_category(app_train, app_test, 'CNT_CHILDREN', (20,20))
plot_stat(app_train, 'CNT_CHILDREN', 'Children count for CSV Train', (15,10))
plot_stat(app_test, 'CNT_CHILDREN', 'Children count for CSV Test', (15,10))
En ce qui concerne le nombre d'enfants, nous pouvons constater que la majorité des clients n'ont pas d'enfant. Plus de 20% des clients ont 1 enfant, 8% en ont 2 et 1% en ont 3. Les proportions sont assez équivalentes entre le test d'entraînement et le test d'essai.
Family Status - Distribution du status familial
test_train_col_category(app_train, app_test, 'NAME_FAMILY_STATUS', (20,20))
La grande majorité des clients sont mariés ou en couple.
plot_stat(app_train, 'NAME_FAMILY_STATUS', 'Family status for CSV Train', (15,10))
plot_stat(app_test, 'NAME_FAMILY_STATUS', 'Family status for CSV Test', (15,10))
plot_percent_target1(app_train, 'NAME_FAMILY_STATUS',"Family status depend on Target1", (15,10))
Le mariage civil a le pourcentage le plus élevé de non-remboursement
Income type - Distribution du type de revenus
plot_stat(app_train, 'NAME_INCOME_TYPE', 'Income type for CSV Train', (15,10))
La très grande majorité des clients a un emploi. La plupart des clients ont des revenus de type travail, d'associé commercial, de retraite.
plot_stat(app_test, 'NAME_INCOME_TYPE', 'Income type for CSV Test', (15,10))
plot_percent_target1(app_train, 'NAME_INCOME_TYPE',"Income type depend on Target1", (15,10))
Les prêts sont non remboursés avec les clients qui ont des revenus de congé maternité et de chômage.
Type de travail - Distribution du type de travail des clients
plot_stat(app_train, 'OCCUPATION_TYPE', 'Client\'s occupation for CSV Train', (15,10))
La plupart des clients sont des ouvriers.
plot_stat(app_test, 'OCCUPATION_TYPE', 'Client\'s occupation for CSV Test', (15,10))
plot_percent_target1(app_train, 'OCCUPATION_TYPE',"Occupation type depend on Target1", (15,10))
Les prêts sont non remboursés avec les clients qui sont des ouviers peu qualifiés.
Type d'éducation - Distribution du type d'éducation des clients
plot_stat(app_train, 'NAME_EDUCATION_TYPE', 'Education type for CSV Train', (15,10))
La majorité des clients ont une éducation de niveau secondaire et supérieure.
plot_stat(app_test, 'NAME_EDUCATION_TYPE', 'Education type for CSV Test', (15,10))
plot_percent_target1(app_train, 'NAME_EDUCATION_TYPE',"Education type depend on Target1", (15,10))
Les clients ayant un niveau d'éducation de début de secondaire risquent de moins rembourser les prêts que les clients ayant une éducation universitaire.
Type de logement - Distribution du type de logement des clients
plot_stat(app_train, 'NAME_HOUSING_TYPE', 'Type of house for CSV Train', (15,10))
La majorité des clients vivent en maison ou en appartement.
plot_stat(app_test, 'NAME_HOUSING_TYPE', 'Type of house for CSV Test', (15,10))
plot_percent_target1(app_train, 'NAME_HOUSING_TYPE',"Type of house depend on Target1", (15,10))
Les clients qui payent un loyer ou qui vivent chez leurs parents ont plus de mal à rembourser un prêt.
Montant crédit moyen :
target_0 =app_train.loc[app_train['TARGET'] == 0]
target_0['AMT_CREDIT'].mean()
Le montant moyen des crédit est de 602 k€ pour les personnes sachant rembourser le prêt.
target_1 =app_train.loc[app_train['TARGET'] == 1]
target_1['AMT_CREDIT'].mean()
Le montant moyen des crédit est de 557 k€ pour les personnes ne sachant pas rembourser leurs prêts.
Le coefficient de corrélation n'est pas la meilleure méthode pour représenter la "pertinence" d'une caractéristique, mais il nous donne une idée des relations possibles au sein des données. Voici quelques interprétations générales de la valeur absolue du coefficient de corrélation :
0,00-0,19 "très faible" 0,20 à 0,39 "faible". 0,40-0,59 "modéré 0,60-0,79 "fort 0,80-1,0 "très forte".
Voyons les relations possibles entre les variables et le TARGET en calculant le coefficiant de Pearson.
# Find correlations with the target and sort
correlations = app_train.corr()['TARGET'].sort_values()
# Display correlations
print('Most Positive Correlations:\n', correlations.tail(15))
print('\nMost Negative Correlations:\n', correlations.head(15))
TARGET a la plus forte corrélation positive avec 'DAYS_BIRTH'.
# Find the correlation of the positive days since birth and target
app_train['DAYS_BIRTH'] = abs(app_train['DAYS_BIRTH'])
app_train['DAYS_BIRTH'].corr(app_train['TARGET'])
Au fur et à mesure que le client vieillit, il existe une relation linéaire négative avec l'objectif, ce qui signifie que plus les clients vieillissent, plus ils ont tendance à rembourser leurs prêts dans les délais.
On trouve une corrélation négative, entre la taget et l'âge des clients. Plus le client est âgé moins la probabilité de défaut de paiement est haute.
Effet de l'âge sur la TARGET
plt.figure(figsize = (15, 10))
# KDE plot ages when there is no default
sns.kdeplot(app_train.loc[app_train['TARGET'] == 0, 'DAYS_BIRTH'] / 365, label = 'target = 0')
# KDE plot ages when there is default
sns.kdeplot(app_train.loc[app_train['TARGET'] == 1, 'DAYS_BIRTH'] / 365, label = 'target = 1')
plt.xlabel('Age (years)'); plt.ylabel('Density')
plt.title('Distribution des âges', weight='bold', size=18)
plt.legend()
La courbe de la TARGET == 1 penche vers l'extrémité la plus jeune de la fourchette, ce qui signifierai que les personnes jeunes ont plus de mal à rembourser. Cette variable sera probablement utile dans un modèle d'apprentissage automatique car elle affecte la cible.
ratio de prêts non remboursés dans chaque tranche d'âge (par 5 ans)
age_data = app_train[['TARGET', 'DAYS_BIRTH']]
age_data['YEARS_BIRTH'] = age_data['DAYS_BIRTH'] / 365
# Découpage par tranche d'âge.
age_data['YEARS_BINNED'] = pd.cut(age_data['YEARS_BIRTH'], bins = np.linspace(20, 70, num = 11))
# On regroupe par tranche crée
age_groups = age_data.groupby('YEARS_BINNED').mean()
age_groups
Pour chaque tranche d'âge de 5 ans, nous avons la moyenne de TARGET (c'est à dire la moyenne de 1 (défaut de paiement) recensée pour chaque tranche d'âge), l'année moyenne de naissance par goupe.
Nous découpons d'abord la catégorie d'âge en tranches de 5 ans chacune. Ensuite, pour chaque bac, nous calculons la valeur moyenne de la cible, ce qui nous indique le ratio de prêts non remboursés dans chaque catégorie d'âge.
échec moyen du remboursement des prêts par tranche d'âge.
plt.figure(figsize=(15, 10))
# Graph the age bins and the average of the target as a bar plot
sns.barplot(age_groups.index.astype(str), 100 * age_groups['TARGET'])
# Plot labeling
plt.xticks(rotation = 75)
plt.xlabel('Age Group (years)', weight='bold')
plt.ylabel('Default of payment (%)', weight='bold')
plt.title("Default by age group",
weight='bold', size=18)
Les clients les plus jeunes sont plus susceptibles à ne pas rembourser le prêt.
Sources extérieures, les plus fortes corrélations linéaires négatives …
Ces 3 variables (EXT_SOURCE) présentant les corrélations négatives les plus fortes avec la Target. Selon la documentation, ces fonctionnalités représentent un «score normalisé à partir d'une source de données externes». Difficile de comprendre le sens exact, nous pouvons émettre l'hypothèse d'une côte de crédit cumulative établie à l'aide de différentes sources de données.
ext_data = app_train[['TARGET', 'EXT_SOURCE_1', 'EXT_SOURCE_2', 'EXT_SOURCE_3', 'DAYS_BIRTH']]
ext_data_corrs = ext_data.corr()
ext_data_corrs
plt.figure(figsize = (15, 10))
# Heatmap of correlations
sns.heatmap(ext_data_corrs, cmap = plt.cm.RdYlBu_r, vmin = -0.25, annot = True, vmax = 0.6)
plt.title('Correlation Heatmap')
Les trois caractéristiques EXT_SOURCE ont des corrélations négatives avec la cible, ce qui indique que plus la valeur de l'EXT_SOURCE augmente, plus le client est susceptible de rembourser le prêt. Nous pouvons également voir que DAYS_BIRTH est positivement corrélé avec les EXT_SOURCE, ce qui indique que l'un des facteurs de ces scores est peut-être l'âge du client.
plt.figure(figsize = (10, 12))
# iterate through the sources
for i, source in enumerate(['EXT_SOURCE_1', 'EXT_SOURCE_2', 'EXT_SOURCE_3']):
# create a new subplot for each source
plt.subplot(3, 1, i + 1)
# plot repaid loans
sns.kdeplot(app_train.loc[app_train['TARGET'] == 0, source], label = 'target == 0')
# plot loans that were not repaid
sns.kdeplot(app_train.loc[app_train['TARGET'] == 1, source], label = 'target == 1')
# Label the plots
plt.title('Distribution of %s by Target Value' % source)
plt.xlabel('%s' % source); plt.ylabel('Density');
plt.tight_layout(h_pad = 2.5)
EXT_SOURCE_3 affiche la plus grande différence entre les valeurs de la cible. Nous pouvons clairement voir que cette caractéristique a une certaine relation avec la probabilité qu'un demandeur rembourse un prêt. La relation n'est pas très forte (en fait, elles sont toutes considérées comme très faibles), mais ces variables seront toujours utiles pour un modèle d'apprentissage automatique permettant de prédire si un demandeur remboursera ou non un prêt à temps.
#Plot distribution of one feature
def plot_distribution(dataframe, feature, title, size):
plt.figure(figsize=size)
t0 = dataframe.loc[dataframe['TARGET'] == 0]
t1 = dataframe.loc[dataframe['TARGET'] == 1]
sns.kdeplot(t0[feature].dropna(), color='blue', label="TARGET = 0")
sns.kdeplot(t1[feature].dropna(), color='red', label="TARGET = 1")
plt.title(title)
plt.ylabel('')
plt.legend()
plt.show()
plot_distribution(app_train, 'AMT_CREDIT', "Credit distribution", (20,6))
print(" -------------------------------------------------------")
plot_distribution(app_train,'AMT_ANNUITY', "Annuity distribution", (20,6))
print(" -------------------------------------------------------")
plot_distribution(app_train,'AMT_GOODS_PRICE', "Goods price distribution", (20,6))
print(" -------------------------------------------------------")
plot_distribution(app_train,'DAYS_REGISTRATION', "Days of registration distribution", (20,6))
Tous les crédits précédents du client fournis par d'autres institutions financières qui ont été rapportés au Credit Bureau (pour les clients qui ont un prêt dans notre échantillon). Pour chaque prêt dans notre échantillon, il y a autant de lignes que le nombre de crédits que le client avait dans le Credit Bureau avant la date de la demande. SK_ID_CURR est la clé reliant les données application_train | test aux données du bureau.
Il est nécessaire de fusionner "application_train" avec "bureau" pour pouvoir collecter des informations justifiant la TARGET == 1 pour chaque client.
app_train.shape
bureau.head()
Nous retrouvons ici la notation XNA qui doit être remplacée par NaN (erreur de représentation NaN).
bureau = bureau.replace('XNA', np.nan)
application_bureau_train = app_train.merge(bureau, left_on='SK_ID_CURR', right_on='SK_ID_CURR', how='inner')
application_bureau_train.shape
Credi_active - Distribution du statut des crédits
#CREDIT_ACTIVE
plot_stat(application_bureau_train, 'CREDIT_ACTIVE',"Credit status distribution", (15,10))
print(" -------------------------------------------------------")
plot_percent_target1(application_bureau_train, 'CREDIT_ACTIVE',"Credit status distribution depend on Target1", (15,10))
credit currency - Distribution devise du crédit
#CREDIT_CURRENCY
plot_stat(application_bureau_train, 'CREDIT_CURRENCY',"Credit currency distribution", (15,10))
print(" -------------------------------------------------------")
plot_percent_target1(application_bureau_train, 'CREDIT_CURRENCY',"Credit currency distribution depend on Target1", (15,10))
credit type - Distribution du type de crédit
#CREDIT_TYPE
plot_stat(application_bureau_train, 'CREDIT_TYPE',"Credit type distribution", (15,10))
print(" -------------------------------------------------------")
plot_percent_target1(application_bureau_train, 'CREDIT_TYPE',"Credit type distribution %Target1", (15,10))
"previous_application" contient des informations sur toutes les demandes précédentes de crédit immobilier des clients qui ont des prêts dans l'échantillon. Il y a une ligne pour chaque demande précédente liée aux prêts dans notre échantillon de données. SK_ID_CURR est la clé reliant les données application_train | test aux données previous_application.
Il est nécessaire de fusionner "application_train" avec "previous_application" pour pouvoir collecter des informations justifiant la TARGET == 1 pour chaque client.
app_train.shape
previous_application.head()
Nous retrouvons ici la notation XNA qui doit être remplacée par NaN (erreur de représentation NaN).
previous_application = previous_application.replace('XNA', np.nan)
application_prev_train = app_train.merge(previous_application,
left_on='SK_ID_CURR', right_on='SK_ID_CURR', how='inner')
application_prev_train.shape
Name contract type - Distribution du type des contrats
#NAME_CONTRACT_TYPE_y
plot_stat(application_prev_train, 'NAME_CONTRACT_TYPE_y',"Contract type distribution", (15,10))
print(" -------------------------------------------------------")
plot_percent_target1(application_prev_train, 'NAME_CONTRACT_TYPE_y',"Contract type distribution depend on Target1", (15,10))
Name contract status - Distribution du status des contrats
#NAME_CONTRACT_STATUS
plot_stat(application_prev_train, 'NAME_CONTRACT_STATUS',"Contract status distribution", (15,10))
print(" -------------------------------------------------------")
plot_percent_target1(application_prev_train, 'NAME_CONTRACT_STATUS',"Contract status distribution depend on Target1", (15,10))
Name payment type - Distribution du mode de paiement que le client a choisi pour payer la demande précédente
#NAME_PAYMENT_TYPE
plot_stat(application_prev_train, 'NAME_PAYMENT_TYPE',"Payment type distribution", (15,10))
print(" -------------------------------------------------------")
plot_percent_target1(application_prev_train, 'NAME_PAYMENT_TYPE',"Payment type distribution depend on Target1", (15,10))
Le mode de paiement se fait majoritairement en Cash via la banque.
Le défaut de remboursement ne se dintingue sur aucun type de paiement, l'égalité est quasi parfaite.
Name client type - Le client était-il un ancien ou un nouveau client lors de la demande précédente
#NAME_CLIENT_TYPE
plot_stat(application_prev_train, 'NAME_CLIENT_TYPE',"Client type distribution", (15,10))
print(" -------------------------------------------------------")
plot_percent_target1(application_prev_train, 'NAME_CLIENT_TYPE',"Client type distribution depend on Target1", (15,10))
Dans le jeu de données, les clients majoritaires sont ceux qui font régulièrement des demandes de prêts mais ce sont les nouveaux clients qui ont dû mal à rembourser leurs prêts.
data = app_train.append(app_test)
print('Train:' + str(app_train.shape))
print('Test:' + str(app_test.shape))
print('>>> Data:' + str(data.shape))
bureau : bureau.csv
display(bureau.head())
display(bureau.shape)
Calcul du nombre total des précédents crédits pour chaque client.
PREVIOUS_APPLICATION_COUNT : Nombre de demandes antérieures des clients au crédit immobilier
#Nombre total de demandes précédentes pris par chaque client.
previous_application_counts = bureau.groupby('SK_ID_CURR', as_index=False)['SK_ID_BUREAU'].count().rename(
columns = {'SK_ID_BUREAU': 'PREVIOUS_APPLICATION_COUNT'})
previous_application_counts.head()
#Fusionner cette nouvelle colonne dans notre échantillon de données
data = data.merge(previous_application_counts, on='SK_ID_CURR', how='left')
data.shape
most_credit_type = pd.DataFrame()
most_credit_type = bureau[['SK_ID_CURR',
'CREDIT_TYPE']].copy()
most_credit_type
def mode_perso(serie_values):
#En entrée une serie en sortie une valeur de l'agregation de cette série
count = serie_values.value_counts()
return count.idxmax()
most_credit_type_mode = most_credit_type.groupby(by="SK_ID_CURR").agg(mode_perso)
most_credit_type_mode
most_credit_type_mode = most_credit_type_mode.reset_index()
most_credit_type_mode.rename(columns={
'CREDIT_TYPE': 'MOST_CREDIT_TYPE'}, inplace=True)
left_df = data
right_df = most_credit_type_mode
data = pd.merge(left_df, right_df, on='SK_ID_CURR', how='left')
data.head()
previous_application : Toutes les demandes précédentes de prêts pour le crédit immobilier des clients qui ont des prêts dans notre échantillon. Il y a une ligne pour chaque demande antérieure liée aux prêts dans notre échantillon de données.
display(previous_application.head())
display(previous_application.shape)
PREVIOUS_LOANS_COUNT from previous_application.csv: Nombre total des précédents crédits pris par chaque client
#Number of previous applications of the clients to Home Credit
previous_loan_counts = previous_application.groupby('SK_ID_CURR',
as_index=False)['SK_ID_PREV'].count().rename(
columns = {'SK_ID_PREV': 'PREVIOUS_LOANS_COUNT'})
previous_loan_counts.head()
#Merge this new column in our data sample
data = data.merge(previous_loan_counts, on='SK_ID_CURR', how='left')
data.shape
print('data shape : ', data.shape)
CREDIT_PERCENT_INCOME : le pourcentage du montant du crédit par rapport au revenu du client.
ANNUITY_CREDIT_PERCENT_INCOME : le pourcentage de l'annuité du prêt par rapport au revenu du client.
CREDIT_REFUND_TIME : la durée que va mettre un client à rembourser un prêt en année de crédit (l'annuité étant le montant annuel dû).
DAYS_EMPLOYED_PERCENT : le pourcentage des jours d'emploi par rapport à l'âge du client.
#Pourcentage du montant final du crédit par rapport au revenus total
data['CREDIT_PERCENT_INCOME'] = data['AMT_CREDIT'] / data['AMT_INCOME_TOTAL']
#Pourcentage remboursement crédit sur les revenus total
data['ANNUITY_CREDIT_PERCENT_INCOME'] = data['AMT_ANNUITY'] / data['AMT_INCOME_TOTAL']
#DUREE DE REMBOURSEMENT DU CREDIT : PRIX TOTAL CREDIT / PRIX PAIEMENT PAR AN
data['CREDIT_REFUND_TIME'] = data['AMT_CREDIT'] / data['AMT_ANNUITY']
#POURCENTAGE DE JOURS TRAVAILLES
data['DAYS_EMPLOYED_PERCENT'] = data['DAYS_EMPLOYED'] / data['DAYS_BIRTH']
print('data shape : ', data.shape)
plot_distribution(data,'CREDIT_PERCENT_INCOME', "Percentage of credit amount in relation to client's income", (20,6))
print(" -------------------------------------------------------")
plot_distribution(data,'ANNUITY_CREDIT_PERCENT_INCOME', "Percentage of loan annuity to client income", (20,6))
print(" -------------------------------------------------------")
plot_distribution(data,'CREDIT_REFUND_TIME', "Duration of payment in months", (20,6))
print(" -------------------------------------------------------")
plot_distribution(data,'DAYS_EMPLOYED_PERCENT', "Percentage of days of employment in relation to client's age", (20,6))
data = data.replace(' ', '_', regex=True)
data_train = data[data['SK_ID_CURR'].isin(app_train["SK_ID_CURR"])]
data_test = data[data['SK_ID_CURR'].isin(app_test["SK_ID_CURR"])]
data_test = data_test.drop('TARGET', axis=1)
print('Training Features shape origin: ', app_train.shape)
print('Testing Features shape origin: ', app_test.shape)
print('Training Features shape after merging: ', data_train.shape)
print('Testing Features shape after merging: ', data_test.shape)
find_rate = data_train.copy()
find_rate = find_rate.replace(to_replace = '^nan$', value = np.nan, regex=True)
nb_lines = find_rate.shape[0]
nb_columns = find_rate.shape[1]
find_rate['taux_remplissage_lines'] = (data_train.apply(lambda x: x.count(), axis=1)/nb_columns)
filling_rate = []
remove_line = []
for i in range(0, 11, 1):
taux_remplissage = i/10.0
filling_rate.append(taux_remplissage*100)
df_2 = find_rate[find_rate['taux_remplissage_lines'] > taux_remplissage]
#number of lines in the end
nb_lines_supp = nb_lines - df_2.shape[0]
remove_line.append(nb_lines_supp)
del df_2['taux_remplissage_lines']
find_rate = pd.DataFrame(
{'filling_rate': filling_rate,
'remove_lines': remove_line
})
find_rate
sns.lineplot(data=find_rate, x="filling_rate", y="remove_lines")
def filtration_line(dataframe, taux_remplissage):
df = dataframe.copy()
dataframe = dataframe.replace(to_replace = '^nan$', value = np.nan, regex=True)
#number of line at origin
nb_lines = dataframe.shape[0]
df['taux_remplissage_lines'] = (dataframe.apply(lambda x: x.count(), axis=1)/nb_columns)
df_2 = df[df['taux_remplissage_lines'] > taux_remplissage]
#number of lines in the end
nb_lines_supp = nb_lines - df_2.shape[0]
print("Number of lines with a fill rate higher than {:.2%} : {} lines.".format(taux_remplissage, df_2.shape[0]))
print("Number of lines deleted : {} lines".format(nb_lines_supp))
print(df_2.shape)
del df_2['taux_remplissage_lines']
return df_2
app_train_clean_lines = filtration_line(data_train, 0.7)
app_train_clean_lines.shape
app_train_clean_lines.head()
app_test_clean_lines = filtration_line(data_test, 0.7)
find_rate = app_train_clean_lines.copy()
find_rate = find_rate.replace(to_replace = '^nan$', value = np.nan, regex=True)
nb_colonne = find_rate.shape[1]
filling_rate = []
remove_col = []
for i in range(0, 11, 1):
taux_remplissage = i/10.0
filling_rate.append(taux_remplissage*100)
df = find_rate[find_rate.columns[1-find_rate.isnull().mean() > taux_remplissage]]
#number of columns at the end
nb_colonne_supp = nb_colonne - df.shape[1]
remove_col.append(nb_colonne_supp)
find_rate = pd.DataFrame(
{'filling_rate': filling_rate,
'remove_columns': remove_col
})
find_rate
sns.lineplot(data=find_rate, x="filling_rate", y="remove_columns")
def filtration_columns(dataframe, taux_remplissage):
dataframe = dataframe.replace(to_replace = '^nan$', value = np.nan, regex=True)
#number of columns at origin
nb_colonne = dataframe.shape[1]
df = dataframe[dataframe.columns[1-dataframe.isnull().mean() > taux_remplissage]]
#number of columns at the end
nb_colonne_supp = nb_colonne - df.shape[1]
print("Nombre de colonnes avec un taux de remplissage supérieur à {:.2%} : {} colonnes.".format(taux_remplissage, df.shape[1]))
print("Nombre de colonnes supprimées : {} colonnes".format(nb_colonne_supp))
return df
app_train_reduced = filtration_columns(app_train_clean_lines, 0.8)
app_train_reduced.head()
app_train_reduced.shape
if 'EXT_SOURCE_1' in app_train_reduced.columns:
print("The column EXT_SOURCE_1 is in the datatset.")
else :
app_train_reduced['EXT_SOURCE_1'] = app_train_clean_lines['EXT_SOURCE_1']
print("The column EXT_SOURCE_1 has been added to the dataset.")
if 'EXT_SOURCE_2' in app_train_reduced.columns:
print("The column EXT_SOURCE_2 is in the datatset.")
else :
app_train_reduced['EXT_SOURCE_2'] = app_train_clean_lines['EXT_SOURCE_2']
print("The column EXT_SOURCE_2 has been added to the dataset.")
if 'EXT_SOURCE_3' in app_train_reduced.columns:
print("The column EXT_SOURCE_3 is in the datatset.")
else :
app_train_reduced['EXT_SOURCE_3'] = app_train_clean_lines['EXT_SOURCE_3']
print("The column EXT_SOURCE_3 has been added to the dataset.")
#----------------------------------------------------------------------------
if 'DAYS_EMPLOYED' in app_train_reduced.columns:
print("The column DAYS_EMPLOYED is in the datatset.")
else :
app_train_reduced['DAYS_EMPLOYED'] = app_train_clean_lines['DAYS_EMPLOYED']
print("The column DAYS_EMPLOYED has been added to the dataset.")
#----------------------------------------------------------------------------
if 'DAYS_BIRTH' in app_train_reduced.columns:
print("The column DAYS_BIRTH is in the datatset.")
else :
app_train_reduced['DAYS_BIRTH'] = app_train_clean_lines['DAYS_BIRTH']
print("The column DAYS_BIRTH has been added to the dataset.")
#----------------------------------------------------------------------------
if 'AGE' in app_train_reduced.columns:
print("The column AGE is in the datatset.")
else :
app_train_reduced['AGE'] = app_train_clean_lines['AGE']
print("The column AGE has been added to the dataset.")
app_train_reduced.shape
filter_columns = list(app_train_reduced.columns)
def remove_columns(dataframe, filter_columns):
"""dataframe : dataframe to filter
filter_columns : columns to keep"""
new = pd.DataFrame()
for column in filter_columns:
try:
new[column] = dataframe[column]
except:
print('...colonne non présente : ', column)
print('\n')
print("All selected columns have been kept from the dataset")
return new
app_test_reduced = remove_columns(app_test_clean_lines, filter_columns)
app_test_reduced
#Imputation with pandas
def imputation_pandas(dataframe):
short_cleaned_impute = dataframe.copy()
for col_name in dataframe:
short_cleaned_impute[col_name] = dataframe[col_name].interpolate(method='linear', inplace=False, limit_direction="both").ffill().bfill()
return short_cleaned_impute
TRAIN
app_train_reduced.dtypes.unique()
# Df des features numériques.
df_num_train = app_train_reduced.select_dtypes('number').reset_index(drop = True)
# Df des features catégoriques.
df_categ_train = app_train_reduced.select_dtypes('object').reset_index(drop = True)
Imputation des colonne numérique par la méthode d'interpolation linéaire
df_num_imputed_train = imputation_pandas(df_num_train)
df_num_imputed_train
Imputation colonne catégorielle par son mode
df_categ_train = df_categ_train.apply(lambda x:x.fillna(x.value_counts().index[0]))
app_train_final = pd.concat([df_num_imputed_train, df_categ_train], axis=1)
app_train_final
app_train_final = app_train_final.reset_index()
del app_train_final['index']
TEST
# Df des features numériques.
df_num_test = app_test_reduced.select_dtypes('number').reset_index(drop = True)
# Df des features catégoriques.
df_categ_test = app_test_reduced.select_dtypes('object').reset_index(drop = True)
df_num_imputed_test = imputation_pandas(df_num_test)
df_num_imputed_test
df_categ_test = df_categ_test.apply(lambda x:x.fillna(x.value_counts().index[0]))
app_test_final = pd.concat([df_num_imputed_test, df_categ_test], axis=1)
app_test_final
app_test_final = app_test_final.reset_index()
del app_test_final['index']
app_train_final['SK_ID_CURR']=app_train_final['SK_ID_CURR'].astype('object')
app_train_final['TARGET']=app_train_final['TARGET'].astype('object')
app_test_final['SK_ID_CURR']=app_test_final['SK_ID_CURR'].astype('object')
#app_train_final['DAYS_EMPLOYED'].replace(np.nan, app_train_final['DAYS_EMPLOYED'].mean(), inplace = True)
#app_test_final['DAYS_EMPLOYED'].replace(np.nan, app_test_final['DAYS_EMPLOYED'].mean(), inplace = True)
plt.figure(figsize=(15,10))
sns.distplot(app_train_final['DAYS_EMPLOYED'], hist=True, rug=True, bins=25)
sns.distplot(app_test_final['DAYS_EMPLOYED'], hist=True, rug=True, bins=25)
plt.title('Histogram of DAYS_EMPLOYED after replacing nan with mean of variable for the train and test set',
weight='bold', size=18)
plt.xlabel('Days Employment', weight="bold")
labels= ["Train", "Test"]
plt.legend(labels)
plt.show()
df_num_train = app_train_final.select_dtypes(['number']).reset_index(drop = True)
df_categ_train = app_train_final.select_dtypes('object').reset_index(drop = True)
df_num_test = app_test_final.select_dtypes(['number']).reset_index(drop = True)
df_categ_test = app_test_final.select_dtypes('object').reset_index(drop = True)
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(df_num_train)
df_num_train_train = pd.DataFrame(scaler.transform(df_num_train), index=df_num_train.index, columns=df_num_train.columns)
df_num_test_test = pd.DataFrame(scaler.transform(df_num_test), index=df_num_test.index, columns=df_num_test.columns)
df_app_train = pd.concat([df_categ_train, df_num_train_train], axis=1)
df_app_test = pd.concat([df_categ_test, df_num_test_test], axis=1)
df_app_train
df_app_train.shape
df_app_test.shape
With Standardisation
df_app_train.shape
Pour les variables catégorielles avec de nombreuses classes, one hot encoding est l'approche la plus sûre car elle n'impose pas de valeurs arbitraires aux catégories. Le seul inconvénient du one hot encoding est que le nombre de caractéristiques (dimensions des données) peut exploser avec des variables catégorielles comportant de nombreuses catégories.
Mettons en œuvre la politique décrite ci-dessus : pour toute variable catégorielle (dtype == object) avec 2 catégories uniques, nous utiliserons l'encodage par label, et pour toute variable catégorielle avec plus de 2 catégories uniques, nous utiliserons l'encodage one-hot.
# Create a label encoder object
le = LabelEncoder()
le_count = 0
oh_count = 0
# Iterate through the columns
for col in df_app_train:
if col == 'TARGET' or col == 'SK_ID_CURR':
pass
else :
if df_app_train[col].dtype == 'object':
# If 2 or fewer unique categories
if len(list(df_app_train[col].unique())) <= 2:
# Train on the training data
le.fit(df_app_train[col])
# Transform both training and testing data
df_app_train[col] = le.transform(df_app_train[col])
df_app_test[col] = le.transform(df_app_test[col])
# Keep track of how many columns were label encoded
le_count += 1
else :
#else : one hot encoding
df_app_train = pd.get_dummies(df_app_train, prefix=[col], columns=[col])
df_app_test = pd.get_dummies(df_app_test, prefix=[col], columns=[col])
oh_count += 1
print('%d columns were label encoded.' % le_count)
print('Training Features shape: ', df_app_train.shape)
print('Testing Features shape: ', df_app_test.shape)
print('%d columns were one hot encoded.' % oh_count)
Il doit y avoir les mêmes caractéristiques (colonnes) dans les données de formation et de test. L'encodage a créé plus de colonnes dans les données d'apprentissage car certaines variables catégorielles avaient plus de catégories non représentées que dans les données de test. Pour supprimer les colonnes dans les données d'apprentissage qui ne sont pas dans les données de test, nous devons aligner les cadres de données. Tout d'abord, nous extrayons la colonne cible des données de formation (car elle ne figure pas dans les données de test mais nous devons conserver cette information). Lorsque nous effectuons l'alignement, nous devons nous assurer de définir axis = 1 pour aligner les cadres de données sur les colonnes et non sur les lignes.
train_labels = df_app_train['TARGET']
# Align the training and testing data, keep only columns present in both dataframes
df_app_train, df_app_test = df_app_train.align(df_app_test, join = 'inner', axis = 1)
# Add the target back in
df_app_train['TARGET'] = train_labels
print('Training Features shape: ', df_app_train.shape)
print('Testing Features shape: ', df_app_test.shape)
df_app_train
Without Standardisation
# Create a label encoder object
le = LabelEncoder()
le_count = 0
oh_count = 0
# Iterate through the columns
for col in app_train_final:
if col == 'TARGET' or col == 'SK_ID_CURR':
pass
else :
if app_train_final[col].dtype == 'object':
# If 2 or fewer unique categories
if len(list(app_train_final[col].unique())) <= 2:
# Train on the training data
le.fit(app_train_final[col])
# Transform both training and testing data
app_train_final[col] = le.transform(app_train_final[col])
app_test_final[col] = le.transform(app_test_final[col])
# Keep track of how many columns were label encoded
le_count += 1
else :
#else : one hot encoding
app_train_final = pd.get_dummies(app_train_final, prefix=[col], columns=[col])
app_test_final = pd.get_dummies(app_test_final, prefix=[col], columns=[col])
oh_count += 1
print('%d columns were label encoded.' % le_count)
print('Training Features shape: ', app_train_final.shape)
print('Testing Features shape: ', app_test_final.shape)
print('%d columns were one hot encoded.' % oh_count)
train_labels = app_train_final['TARGET']
# Align the training and testing data, keep only columns present in both dataframes
app_train_final, app_test_final = app_train_final.align(app_test_final, join = 'inner', axis = 1)
# Add the target back in
app_train_final['TARGET'] = train_labels
print('Training Features shape: ', app_train_final.shape)
print('Testing Features shape: ', app_test_final.shape)
app_train_final
app_train_final.to_csv("train_imputed_without_standardisation.csv", index=False)
app_test_final.to_csv("test_imputed_without_standardisation.csv", index=False)
df_app_train.to_csv("df_train_imputed.csv", index=False)
df_app_test.to_csv("df_test_imputed.csv", index=False)
app_train_reduced.to_csv("real_data_clean_train.csv", index=False)
app_test_reduced.to_csv("real_data_clean_test.csv", index=False)
app_train_reduced
df_app_train
app_train_final